// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/touchui/touch_selection_menu_runner_views.h"

#include <stddef.h>

#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/text_utils.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/layout/box_layout.h"

namespace views {
namespace {

    const int kMenuCommands[] = { IDS_APP_CUT, IDS_APP_COPY, IDS_APP_PASTE };
    const int kSpacingBetweenButtons = 2;
    const int kButtonSeparatorColor = SkColorSetARGB(13, 0, 0, 0);
    const int kMenuButtonMinHeight = 38;
    const int kMenuButtonMinWidth = 63;
    const int kMenuMargin = 1;

    const char kEllipsesButtonText[] = "...";
    const int kEllipsesButtonTag = -1;

} // namespace

// A bubble that contains actions available for the selected text. An object of
// this type, as a BubbleDelegateView, manages its own lifetime.
class TouchSelectionMenuRunnerViews::Menu : public BubbleDelegateView,
                                            public ButtonListener {
public:
    Menu(TouchSelectionMenuRunnerViews* owner,
        ui::TouchSelectionMenuClient* client,
        const gfx::Rect& anchor_rect,
        const gfx::Size& handle_image_size,
        aura::Window* context);

    // Checks whether there is any command available to show in the menu.
    static bool IsMenuAvailable(const ui::TouchSelectionMenuClient* client);

    // Closes the menu. This will eventually self-destroy the object.
    void Close();

private:
    friend class TouchSelectionMenuRunnerViews::TestApi;

    ~Menu() override;

    // Queries the |client_| for what commands to show in the menu and sizes the
    // menu appropriately.
    void CreateButtons();

    // Helper method to create a single button.
    Button* CreateButton(const base::string16& title, int tag);

    // Helper to disconnect this menu object from its owning menu runner.
    void DisconnectOwner();

    // BubbleDelegateView:
    void OnPaint(gfx::Canvas* canvas) override;
    void WindowClosing() override;

    // ButtonListener:
    void ButtonPressed(Button* sender, const ui::Event& event) override;

    TouchSelectionMenuRunnerViews* owner_;
    ui::TouchSelectionMenuClient* const client_;

    DISALLOW_COPY_AND_ASSIGN(Menu);
};

TouchSelectionMenuRunnerViews::Menu::Menu(TouchSelectionMenuRunnerViews* owner,
    ui::TouchSelectionMenuClient* client,
    const gfx::Rect& anchor_rect,
    const gfx::Size& handle_image_size,
    aura::Window* context)
    : BubbleDelegateView(nullptr, BubbleBorder::BOTTOM_CENTER)
    , owner_(owner)
    , client_(client)
{
    DCHECK(owner_);
    DCHECK(client_);

    set_shadow(BubbleBorder::SMALL_SHADOW);
    set_parent_window(context);
    set_margins(gfx::Insets(kMenuMargin, kMenuMargin, kMenuMargin, kMenuMargin));
    set_can_activate(false);
    set_adjust_if_offscreen(true);
    EnableCanvasFlippingForRTLUI(true);

    SetLayoutManager(
        new BoxLayout(BoxLayout::kHorizontal, 0, 0, kSpacingBetweenButtons));
    CreateButtons();

    // After buttons are created, check if there is enough room between handles to
    // show the menu and adjust anchor rect properly if needed, just in case the
    // menu is needed to be shown under the selection.
    gfx::Rect adjusted_anchor_rect(anchor_rect);
    int menu_width = GetPreferredSize().width();
    // TODO(mfomitchev): This assumes that the handles are center-aligned to the
    // |achor_rect| edges, which is not true. We should fix this, perhaps by
    // passing down the cumulative width occupied by the handles within
    // |anchor_rect| plus the handle image height instead of |handle_image_size|.
    // Perhaps we should also allow for some minimum padding.
    if (menu_width > anchor_rect.width() - handle_image_size.width())
        adjusted_anchor_rect.Inset(0, 0, 0, -handle_image_size.height());
    SetAnchorRect(adjusted_anchor_rect);

    BubbleDelegateView::CreateBubble(this);
    GetWidget()->Show();
}

bool TouchSelectionMenuRunnerViews::Menu::IsMenuAvailable(
    const ui::TouchSelectionMenuClient* client)
{
    DCHECK(client);

    for (size_t i = 0; i < arraysize(kMenuCommands); i++) {
        if (client->IsCommandIdEnabled(kMenuCommands[i]))
            return true;
    }
    return false;
}

TouchSelectionMenuRunnerViews::Menu::~Menu()
{
}

void TouchSelectionMenuRunnerViews::Menu::CreateButtons()
{
    for (size_t i = 0; i < arraysize(kMenuCommands); i++) {
        int command_id = kMenuCommands[i];
        if (!client_->IsCommandIdEnabled(command_id))
            continue;

        Button* button = CreateButton(l10n_util::GetStringUTF16(command_id), command_id);
        AddChildView(button);
    }

    // Finally, add ellipses button.
    AddChildView(
        CreateButton(base::UTF8ToUTF16(kEllipsesButtonText), kEllipsesButtonTag));
    Layout();
}

Button* TouchSelectionMenuRunnerViews::Menu::CreateButton(
    const base::string16& title,
    int tag)
{
    base::string16 label = gfx::RemoveAcceleratorChar(title, '&', nullptr, nullptr);
    LabelButton* button = new LabelButton(this, label);
    button->SetMinSize(gfx::Size(kMenuButtonMinWidth, kMenuButtonMinHeight));
    button->SetFocusable(true);
    button->set_request_focus_on_press(false);
    const gfx::FontList& font_list = ui::ResourceBundle::GetSharedInstance().GetFontList(
        ui::ResourceBundle::SmallFont);
    button->SetFontList(font_list);
    button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    button->set_tag(tag);
    return button;
}

void TouchSelectionMenuRunnerViews::Menu::Close()
{
    DisconnectOwner();
    // Closing the widget will self-destroy this object.
    Widget* widget = GetWidget();
    if (widget && !widget->IsClosed())
        widget->Close();
}

void TouchSelectionMenuRunnerViews::Menu::DisconnectOwner()
{
    DCHECK(owner_);
    owner_->menu_ = nullptr;
    owner_ = nullptr;
}

void TouchSelectionMenuRunnerViews::Menu::OnPaint(gfx::Canvas* canvas)
{
    BubbleDelegateView::OnPaint(canvas);

    // Draw separator bars.
    for (int i = 0; i < child_count() - 1; ++i) {
        View* child = child_at(i);
        int x = child->bounds().right() + kSpacingBetweenButtons / 2;
        canvas->FillRect(gfx::Rect(x, 0, 1, child->height()),
            kButtonSeparatorColor);
    }
}

void TouchSelectionMenuRunnerViews::Menu::WindowClosing()
{
    DCHECK(!owner_ || owner_->menu_ == this);
    BubbleDelegateView::WindowClosing();
    if (owner_)
        DisconnectOwner();
}

void TouchSelectionMenuRunnerViews::Menu::ButtonPressed(
    Button* sender,
    const ui::Event& event)
{
    Close();
    if (sender->tag() != kEllipsesButtonTag)
        client_->ExecuteCommand(sender->tag(), event.flags());
    else
        client_->RunContextMenu();
}

TouchSelectionMenuRunnerViews::TestApi::TestApi(
    TouchSelectionMenuRunnerViews* menu_runner)
    : menu_runner_(menu_runner)
{
    DCHECK(menu_runner_);
}

TouchSelectionMenuRunnerViews::TestApi::~TestApi() { }

gfx::Rect TouchSelectionMenuRunnerViews::TestApi::GetAnchorRect() const
{
    TouchSelectionMenuRunnerViews::Menu* menu = menu_runner_->menu_;
    return menu ? menu->GetAnchorRect() : gfx::Rect();
}

Button* TouchSelectionMenuRunnerViews::TestApi::GetFirstButton() const
{
    TouchSelectionMenuRunnerViews::Menu* menu = menu_runner_->menu_;
    return menu ? static_cast<Button*>(menu->child_at(0)) : nullptr;
}

Widget* TouchSelectionMenuRunnerViews::TestApi::GetWidget() const
{
    TouchSelectionMenuRunnerViews::Menu* menu = menu_runner_->menu_;
    return menu ? menu->GetWidget() : nullptr;
}
TouchSelectionMenuRunnerViews::TouchSelectionMenuRunnerViews()
    : menu_(nullptr)
{
}

TouchSelectionMenuRunnerViews::~TouchSelectionMenuRunnerViews()
{
    CloseMenu();
}

bool TouchSelectionMenuRunnerViews::IsMenuAvailable(
    const ui::TouchSelectionMenuClient* client) const
{
    return TouchSelectionMenuRunnerViews::Menu::IsMenuAvailable(client);
}

void TouchSelectionMenuRunnerViews::OpenMenu(
    ui::TouchSelectionMenuClient* client,
    const gfx::Rect& anchor_rect,
    const gfx::Size& handle_image_size,
    aura::Window* context)
{
    CloseMenu();

    if (TouchSelectionMenuRunnerViews::Menu::IsMenuAvailable(client))
        menu_ = new Menu(this, client, anchor_rect, handle_image_size, context);
}

void TouchSelectionMenuRunnerViews::CloseMenu()
{
    if (!menu_)
        return;

    // Closing the menu sets |menu_| to nullptr and eventually deletes the object.
    menu_->Close();
    DCHECK(!menu_);
}

bool TouchSelectionMenuRunnerViews::IsRunning() const
{
    return menu_ != nullptr;
}

} // namespace views
